/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.sun.corba.se.impl.interceptors;

import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.portable.Delegate;
import org.omg.PortableInterceptor.LOCATION_FORWARD;
import org.omg.PortableInterceptor.SUCCESSFUL;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.omg.PortableInterceptor.TRANSPORT_RETRY;
import org.omg.PortableInterceptor.USER_EXCEPTION;
import org.omg.PortableInterceptor.ClientRequestInfo;
import org.omg.PortableInterceptor.ClientRequestInterceptor;
import org.omg.PortableInterceptor.ForwardRequest;
import org.omg.PortableInterceptor.IORInterceptor;
import org.omg.PortableInterceptor.IORInterceptor_3_0;
import org.omg.PortableInterceptor.ServerRequestInfo;
import org.omg.PortableInterceptor.ServerRequestInterceptor;
import org.omg.PortableInterceptor.ObjectReferenceTemplate;

import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.oa.ObjectAdapter;
import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.impl.orbutil.ORBUtility;
        
/** 
 * Handles invocation of interceptors.  Has specific knowledge of how to
 * invoke IOR, ClientRequest, and ServerRequest interceptors.  
 * Makes use of the InterceptorList to retrieve the list of interceptors to 
 * be invoked.  Most methods in this class are package scope so that they
 * may only be called from the PIHandlerImpl.
 */
public class InterceptorInvoker {

    // The ORB
    private ORB orb;
    
    // The list of interceptors to be invoked
    private InterceptorList interceptorList;

    // True if interceptors are to be invoked, or false if not
    // Note: This is a global enable/disable flag, whereas the enable flag
    // in the RequestInfoStack in PIHandlerImpl is only for a particular Thread.
    private boolean enabled = false;

    // PICurrent variable.
    private PICurrent current;

    // NOTE: Be careful about adding additional attributes to this class.  
    // Multiple threads may be calling methods on this invoker at the same 
    // time.

    /**
     * Creates a new Interceptor Invoker.  Constructor is package scope so
     * only the ORB can create it.  The invoker is initially disabled, and
     * must be explicitly enabled using setEnabled().
     */
    InterceptorInvoker( ORB orb, InterceptorList interceptorList, 
                        PICurrent piCurrent ) 
    {
        this.orb = orb;
	this.interceptorList = interceptorList;
	this.enabled = false;
        this.current = piCurrent;
    }

    /**
     * Enables or disables the interceptor invoker
     */
    void setEnabled( boolean enabled ) {
	this.enabled = enabled;
    }
    
    /*
     **********************************************************************
     * IOR Interceptor invocation
     **********************************************************************/

    /**
     * Called when a new POA is created.
     *
     * @param oa The Object Adapter associated with the IOR interceptor.
     */
    void objectAdapterCreated( ObjectAdapter oa ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    // Create IORInfo object to pass to IORInterceptors:
	    IORInfoImpl info = new IORInfoImpl( oa );

	    // Call each IORInterceptor:
	    IORInterceptor[] iorInterceptors = 
                (IORInterceptor[])interceptorList.getInterceptors( 
                InterceptorList.INTERCEPTOR_TYPE_IOR );
	    int size = iorInterceptors.length;

	    // Implementation note:
	    // This loop counts backwards for greater efficiency.
	    // Benchmarks have shown that counting down is more efficient
	    // than counting up in Java for loops, as a compare to zero is
	    // faster than a subtract and compare to zero.  In this case,
	    // it doesn't really matter much, but it's simply a force of habit.

	    for( int i = (size - 1); i >= 0; i-- ) {
		IORInterceptor interceptor = iorInterceptors[i];
		try {
		    interceptor.establish_components( info );
	        }
		catch( Exception e ) {
		    // as per PI spec (orbos/99-12-02 sec 7.2.1), if
		    // establish_components throws an exception, ignore it.
		}
	    }

	    // Change the state so that only template operations are valid
	    info.makeStateEstablished() ;

	    for( int i = (size - 1); i >= 0; i-- ) {
		IORInterceptor interceptor = iorInterceptors[i];
		if (interceptor instanceof IORInterceptor_3_0) {
		    IORInterceptor_3_0 interceptor30 = (IORInterceptor_3_0)interceptor ;
		    // Note that exceptions here are NOT ignored, as per the
		    // ORT spec (orbos/01-01-04)
		    interceptor30.components_established( info );
		}
	    }

	    // Change the state so that no operations are valid,
	    // in case a reference to info escapes this scope.
	    // This also completes the actions associated with the
	    // template interceptors on this POA.
	    info.makeStateDone() ;
	}
    }

    void adapterManagerStateChanged( int managerId, short newState )
    {
	if (enabled) {
	    IORInterceptor[] interceptors = 
                (IORInterceptor[])interceptorList.getInterceptors( 
                InterceptorList.INTERCEPTOR_TYPE_IOR );
	    int size = interceptors.length;

	    for( int i = (size - 1); i >= 0; i-- ) {
		try {
		    IORInterceptor interceptor = interceptors[i];
		    if (interceptor instanceof IORInterceptor_3_0) {
			IORInterceptor_3_0 interceptor30 = (IORInterceptor_3_0)interceptor ;
			interceptor30.adapter_manager_state_changed( managerId, 
			    newState );
		    }
		} catch (Exception exc) {
		    // No-op: ignore exception in this case
		}
	    }
	}
    }

    void adapterStateChanged( ObjectReferenceTemplate[] templates,
	short newState )
    {
	if (enabled) {
	    IORInterceptor[] interceptors = 
                (IORInterceptor[])interceptorList.getInterceptors( 
                InterceptorList.INTERCEPTOR_TYPE_IOR );
	    int size = interceptors.length;

	    for( int i = (size - 1); i >= 0; i-- ) {
		try {
		    IORInterceptor interceptor = interceptors[i];
		    if (interceptor instanceof IORInterceptor_3_0) {
			IORInterceptor_3_0 interceptor30 = (IORInterceptor_3_0)interceptor ;
			interceptor30.adapter_state_changed( templates, newState );
		    }
		} catch (Exception exc) {
		    // No-op: ignore exception in this case
		}
	    }
	}
    }

    /*
     **********************************************************************
     * Client Interceptor invocation
     **********************************************************************/

    /**
     * Invokes either send_request, or send_poll, depending on the value
     * of info.getStartingPointCall()
     */
    void invokeClientInterceptorStartingPoint( ClientRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// Make a a fresh slot table available to TSC in case
		// interceptors need to make out calls.
		// Client's TSC is now RSC via RequestInfo.
		current.pushSlotTable( );
		info.setPICurrentPushed( true );
		info.setCurrentExecutionPoint( info.EXECUTION_POINT_STARTING );
		
		// Get all ClientRequestInterceptors:
		ClientRequestInterceptor[] clientInterceptors = 
		    (ClientRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_CLIENT );
		int size = clientInterceptors.length;

		// We will assume that all interceptors returned successfully,
		// and adjust the flowStackIndex to the appropriate value if
		// we later discover otherwise.
		int flowStackIndex = size;
		boolean continueProcessing = true;
	
		// Determine whether we are calling send_request or send_poll:
		// (This is currently commented out because our ORB does not 
		// yet support the Messaging specification, so send_poll will
		// never occur.  Once we have implemented messaging, this may
		// be uncommented.)
		// int startingPointCall = info.getStartingPointCall();
		for( int i = 0; continueProcessing && (i < size); i++ ) {
		    try {
			clientInterceptors[i].send_request( info );
			
			// Again, it is not necessary for a switch here, since
			// there is only one starting point call type (see
			// above comment).
			
			//switch( startingPointCall ) {
			//case ClientRequestInfoImpl.CALL_SEND_REQUEST:
			    //clientInterceptors[i].send_request( info );
			    //break;
			//case ClientRequestInfoImpl.CALL_SEND_POLL:
			    //clientInterceptors[i].send_poll( info );
			    //break;
			//}

		    }
		    catch( ForwardRequest e ) {
			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a ForwardRequest, 
			// no other Interceptors' send_request operations are 
			// called.
			flowStackIndex = i;
			info.setForwardRequest( e );
			info.setEndingPointCall( 
			    ClientRequestInfoImpl.CALL_RECEIVE_OTHER );
			info.setReplyStatus( LOCATION_FORWARD.value );
			
			updateClientRequestDispatcherForward( info );
			
			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to work 
			// fine.
			continueProcessing = false;
		    }
		    catch( SystemException e ) {
			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a SystemException, 
			// no other Interceptors' send_request operations are 
			// called.
			flowStackIndex = i;
			info.setEndingPointCall( 
			    ClientRequestInfoImpl.CALL_RECEIVE_EXCEPTION );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );
			info.setException( e );

			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to 
			// work fine.
			continueProcessing = false;
		    } 
		}
		
		// Remember where we left off in the flow stack:
		info.setFlowStackIndex( flowStackIndex );
	    }
	    finally {
		// Make the SlotTable fresh for the next interception point.
		current.resetSlotTable( );
	    }
        } // end enabled check
    }

    /**
     * Invokes either receive_reply, receive_exception, or receive_other,
     * depending on the value of info.getEndingPointCall()
     */
    void invokeClientInterceptorEndingPoint( ClientRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// NOTE: It is assumed someplace else prepared a
		// fresh TSC slot table.

		info.setCurrentExecutionPoint( info.EXECUTION_POINT_ENDING );
		
		// Get all ClientRequestInterceptors:
		ClientRequestInterceptor[] clientInterceptors = 
		    (ClientRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_CLIENT );
		int flowStackIndex = info.getFlowStackIndex();
		
		// Determine whether we are calling receive_reply, 
		// receive_exception, or receive_other:
		int endingPointCall = info.getEndingPointCall();

		// If we would be calling RECEIVE_REPLY, but this is a 
		// one-way call, override this and call receive_other:
		if( ( endingPointCall == 
		      ClientRequestInfoImpl.CALL_RECEIVE_REPLY ) &&
		    info.getIsOneWay() )
		{
		    endingPointCall = ClientRequestInfoImpl.CALL_RECEIVE_OTHER;
		    info.setEndingPointCall( endingPointCall );
		}
		
		// Only step through the interceptors whose starting points
		// have successfully returned.
		// Unlike the previous loop, this one counts backwards for a 
		// reason - we must execute these in the reverse order of the
		// starting points.
		for( int i = (flowStackIndex - 1); i >= 0; i-- ) {
		    
		    try {
			switch( endingPointCall ) {
			case ClientRequestInfoImpl.CALL_RECEIVE_REPLY:
			    clientInterceptors[i].receive_reply( info );
			    break;
			case ClientRequestInfoImpl.CALL_RECEIVE_EXCEPTION:
			    clientInterceptors[i].receive_exception( info );
			    break;
			case ClientRequestInfoImpl.CALL_RECEIVE_OTHER:
			    clientInterceptors[i].receive_other( info );
			    break;
			}
		    }
		    catch( ForwardRequest e ) {

			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a ForwardException, 
			// ending point call changes to receive_other.
			endingPointCall = 
			    ClientRequestInfoImpl.CALL_RECEIVE_OTHER;
			info.setEndingPointCall( endingPointCall );
			info.setReplyStatus( LOCATION_FORWARD.value );
			info.setForwardRequest( e );
			updateClientRequestDispatcherForward( info );
		    }
		    catch( SystemException e ) {

			// as per PI spec (orbos/99-12-02 sec 5.2.1.), if
			// interception point throws a SystemException, 
			// ending point call changes to receive_exception.
			endingPointCall = 
			    ClientRequestInfoImpl.CALL_RECEIVE_EXCEPTION;
			info.setEndingPointCall( endingPointCall );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );
			info.setException( e );
		    } 
		}
	    }
	    finally {
		// See doc for setPICurrentPushed as to why this is necessary.
		// Check info for null in case errors happen before initiate.
		if (info != null && info.isPICurrentPushed()) {
		    current.popSlotTable( );
		    // After the pop, original client's TSC slot table
		    // remains avaiable via PICurrent.
		}
	    }
        } // end enabled check
    }

    /*
     **********************************************************************
     * Server Interceptor invocation
     **********************************************************************/

    /**
     * Invokes receive_request_service_context interception points.
     */
    void invokeServerInterceptorStartingPoint( ServerRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// Make a fresh slot table for RSC.
		current.pushSlotTable();
		info.setSlotTable(current.getSlotTable());

		// Make a fresh slot table for TSC in case
		// interceptors need to make out calls.
		current.pushSlotTable( );

		info.setCurrentExecutionPoint( info.EXECUTION_POINT_STARTING );
		
		// Get all ServerRequestInterceptors:
		ServerRequestInterceptor[] serverInterceptors = 
		    (ServerRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_SERVER );
		int size = serverInterceptors.length;

		// We will assume that all interceptors returned successfully,
		// and adjust the flowStackIndex to the appropriate value if
		// we later discover otherwise.
		int flowStackIndex = size;
		boolean continueProcessing = true;
		
		// Currently, there is only one server-side starting point
		// interceptor called receive_request_service_contexts.
		for( int i = 0; continueProcessing && (i < size); i++ ) {
		    
		    try {
			serverInterceptors[i].
			    receive_request_service_contexts( info );
		    }
		    catch( ForwardRequest e ) {
			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a ForwardRequest, 
			// no other Interceptors' starting points are 
			// called and send_other is called.
			flowStackIndex = i;
			info.setForwardRequest( e );
			info.setIntermediatePointCall(
			    ServerRequestInfoImpl.CALL_INTERMEDIATE_NONE );
			info.setEndingPointCall( 
			    ServerRequestInfoImpl.CALL_SEND_OTHER );
			info.setReplyStatus( LOCATION_FORWARD.value );

			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to work 
			// fine.
			continueProcessing = false;
		    }
		    catch( SystemException e ) {

			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a SystemException, 
			// no other Interceptors' starting points are 
			// called.
			flowStackIndex = i;
			info.setException( e );
			info.setIntermediatePointCall(
			    ServerRequestInfoImpl.CALL_INTERMEDIATE_NONE );
			info.setEndingPointCall( 
			    ServerRequestInfoImpl.CALL_SEND_EXCEPTION );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );

			// For some reason, using break here causes the VM on 
			// NT to lose track of the value of flowStackIndex 
			// after exiting the for loop.  I changed this to 
			// check a boolean value instead and it seems to 
			// work fine.
			continueProcessing = false;
		    }
	    
		}
		
		// Remember where we left off in the flow stack:
		info.setFlowStackIndex( flowStackIndex );
	    }
	    finally {
		// The remaining points, ServantManager and Servant
		// all run in the same logical thread.
		current.popSlotTable( );
		// Now TSC and RSC are equivalent.
	    }
        } // end enabled check
    }

    /**
     * Invokes receive_request interception points
     */
    void invokeServerInterceptorIntermediatePoint( 
        ServerRequestInfoImpl info ) 
    {
        int intermediatePointCall = info.getIntermediatePointCall();
	// If invocation is not yet enabled, don't do anything.
	if( enabled && ( intermediatePointCall != 
                         ServerRequestInfoImpl.CALL_INTERMEDIATE_NONE ) )
        {
	    // NOTE: do not touch the slotStack.  The RSC and TSC are
	    // equivalent at this point.

            info.setCurrentExecutionPoint( info.EXECUTION_POINT_INTERMEDIATE );
            
            // Get all ServerRequestInterceptors:
            ServerRequestInterceptor[] serverInterceptors = 
                (ServerRequestInterceptor[])
                interceptorList.getInterceptors( 
                InterceptorList.INTERCEPTOR_TYPE_SERVER );
            int size = serverInterceptors.length;

            // Currently, there is only one server-side intermediate point
            // interceptor called receive_request.
            for( int i = 0; i < size; i++ ) {

                try {
                    serverInterceptors[i].receive_request( info );
                }
                catch( ForwardRequest e ) {

                    // as per PI spec (orbos/99-12-02 sec 5.3.1.), if
                    // interception point throws a ForwardRequest, 
                    // no other Interceptors' intermediate points are 
                    // called and send_other is called.
		    info.setForwardRequest( e );
                    info.setEndingPointCall( 
                        ServerRequestInfoImpl.CALL_SEND_OTHER );
                    info.setReplyStatus( LOCATION_FORWARD.value );
                    break;
                }
                catch( SystemException e ) {

                    // as per PI spec (orbos/99-12-02 sec 5.3.1.), if
                    // interception point throws a SystemException, 
                    // no other Interceptors' starting points are 
                    // called.
		    info.setException( e );
                    info.setEndingPointCall( 
                        ServerRequestInfoImpl.CALL_SEND_EXCEPTION );
                    info.setReplyStatus( SYSTEM_EXCEPTION.value );
                    break;
                }
            }
        } // end enabled check
    }

    /**
     * Invokes either send_reply, send_exception, or send_other,
     * depending on the value of info.getEndingPointCall()
     */
    void invokeServerInterceptorEndingPoint( ServerRequestInfoImpl info ) {
	// If invocation is not yet enabled, don't do anything.
	if( enabled ) {
	    try {
		// NOTE: do not touch the slotStack.  The RSC and TSC are
		// equivalent at this point.

		// REVISIT: This is moved out to PIHandlerImpl until dispatch
		// path is rearchitected.  It must be there so that
		// it always gets executed so if an interceptor raises
		// an exception any service contexts added in earlier points
		// this point get put in the exception reply (via the SC Q).
		//info.setCurrentExecutionPoint( info.EXECUTION_POINT_ENDING );
		
		// Get all ServerRequestInterceptors:
		ServerRequestInterceptor[] serverInterceptors = 
		    (ServerRequestInterceptor[])interceptorList.
		    getInterceptors( InterceptorList.INTERCEPTOR_TYPE_SERVER );
		int flowStackIndex = info.getFlowStackIndex();
		
		// Determine whether we are calling 
		// send_exception, or send_other:
		int endingPointCall = info.getEndingPointCall();
		
		// Only step through the interceptors whose starting points
		// have successfully returned.
		for( int i = (flowStackIndex - 1); i >= 0; i-- ) {
		    try {
			switch( endingPointCall ) {
			case ServerRequestInfoImpl.CALL_SEND_REPLY:
			    serverInterceptors[i].send_reply( info );
			    break;
			case ServerRequestInfoImpl.CALL_SEND_EXCEPTION:
			    serverInterceptors[i].send_exception( info );
			    break;
			case ServerRequestInfoImpl.CALL_SEND_OTHER:
			    serverInterceptors[i].send_other( info );
			    break;
			}
		    }
		    catch( ForwardRequest e ) {
			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a ForwardException, 
			// ending point call changes to receive_other.
			endingPointCall = 
			    ServerRequestInfoImpl.CALL_SEND_OTHER;
			info.setEndingPointCall( endingPointCall );
			info.setForwardRequest( e );
			info.setReplyStatus( LOCATION_FORWARD.value );
			info.setForwardRequestRaisedInEnding();
		    }
		    catch( SystemException e ) {
			// as per PI spec (orbos/99-12-02 sec 5.3.1.), if
			// interception point throws a SystemException, 
			// ending point call changes to send_exception.
			endingPointCall = 
			    ServerRequestInfoImpl.CALL_SEND_EXCEPTION;
			info.setEndingPointCall( endingPointCall );
			info.setException( e );
			info.setReplyStatus( SYSTEM_EXCEPTION.value );
		    }
		}
		
		// Remember that all interceptors' starting and ending points
		// have already been executed so we need not do anything.
		info.setAlreadyExecuted( true );
	    }
            finally {
		// Get rid of the Server side RSC.
		current.popSlotTable();
	    }
        } // end enabled check
    }
    
    /*
     **********************************************************************
     * Private utility methods
     **********************************************************************/
    
    /**
     * Update the client delegate in the event of a ForwardRequest, given the 
     * information in the passed-in info object.
     */
    private void updateClientRequestDispatcherForward( 
        ClientRequestInfoImpl info ) 
    {
        ForwardRequest forwardRequest = info.getForwardRequestException();
        
        // ForwardRequest may be null if the forwarded IOR is set internal
        // to the ClientRequestDispatcher rather than explicitly through Portable
        // Interceptors.  In this case, we need not update the client 
        // delegate ForwardRequest object.
        if( forwardRequest != null ) {
            org.omg.CORBA.Object object = forwardRequest.forward;

            // Convert the forward object into an IOR:
	    IOR ior = ORBUtility.getIOR( object ) ;
	    info.setLocatedIOR( ior );
        }
    }
    
}
